package org.fxcontainer.controls.ext
{

	import flash.events.Event;
	
	import mx.containers.Form;
	import mx.core.Container;
	import mx.core.mx_internal;
	import mx.events.FlexEvent;
	import mx.events.ValidationResultEvent;
	import mx.validators.Validator;
	
	import org.as3commons.reflect.ClassUtils;
	import org.as3commons.reflect.MethodInvoker;
	import org.as3commons.reflect.Type;
	import org.fxcontainer.controls.IControl;
	import org.fxcontainer.validation.IValidationVisualizer;
	import org.fxcontainer.validation.ValidationVisualizer;

	/**
	 * Framework <code>Form</code> implementation. 
	 * @author Izzet_Mustafayev
	 */
	public class Form extends mx.containers.Form implements IControl
		{
			use namespace mx_internal;
			
			/**
			 * Form constructor. 
			 * 
			 */
			public function Form()
			{
				super();
				addEventListener(FlexEvent.CREATION_COMPLETE, creationCompleteHandler);
				_visualizer = new ValidationVisualizer();
			}
			
			/**
			 * The control name. 
			 */
			private var _controlName:String;
			
			
			/**
			 * The form object to populate with form values.
			 * @default null
			 */
			private var _formObject:Object;
			
			/**
			 * The form class to walk on using reflection to populate formObject.
			 * @default null
			 */
			private var _formObjectClass:Class;
			
			/**
			 * Flag indicates <code>Form</code> initialization state. 
			 */
			private var _initialized:Boolean = false;
			
			/**
			 * Flag to render <code>Form</code> in read-only mode. 
			 */
			private var _readOnly:Boolean = false;
			
			/**
			 * Setable flag to render <code>Form</code> in read-only mode.
			 */
			private var _readOnlySet:Boolean = false;
			
			/**
			 * Reference to <code>ValidationVisualizer</code> implementation instance.
			 */
			private var _visualizer:IValidationVisualizer;
			
			/**
			 * <code>FormItem</code> list. 
			 */
			private var items:Array = new Array();
			
			/**
			 * Retrieves control name if set, otherwise element ID. 
			 * @return control name
			 */
			public function controlName():String
			{
				if (_controlName == ""){
					_controlName = id;
				}
				return this._controlName;
			}
			
			public function set controlname(controlName:String):void
			{
				this._controlName = controlName;
			}
			
			public function set formClass(clazz:Class):void
			{
				_formObjectClass = clazz;
			}
			
			public function get formObject():Object
			{
				return _formObject;
			}
			
			public function get readOnly():Boolean
			{
				return _readOnly;
			}
			
			public function set readOnly(value:Boolean):void
			{
				if (_readOnly==value) return;
				_readOnly = value;
				_readOnlySet = true;
				commitReadOnly();
			}
			
			/**
			 * Triggers <code>Form</code> reset action e.g. restore <code>FormItem</code> values to initial state.
			 */
			public function reset():void
			{
				//TODO: implement functionality
			}
			
			public function submit():Object
			{
				var validationResults:Array = validateAll();
				if(validationResults.length<=0){
					gatherFormObject();
					return _formObject;
				}
				return null;
			}
			
			/**
			 * Triggers form items validators to perform validation.  
			 * @param suppressEvents will be passed to validators instances to 
			 * notify the listeners of the result after validation if <code>false</code>.
			 * @return failed validators list
			 */
			public function validateAll(suppressEvents:Boolean=false):Array {
				var result:Array = [];
				for each(var item:FormItem in items){
					for (var i:int=0; i < item.validators.length;i++){
						var validator:Validator = item.validators[i];
						if (validator.enabled){
							var vRes:ValidationResultEvent = validator.validate(item.data, suppressEvents);
							if (null!=_visualizer) _visualizer.unVisualize(item);
							if (vRes.type != ValidationResultEvent.VALID){
								visualizeValidation(item, vRes);
								result.push(vRes);
							}
						}
					}
				}
				return result;
			}
			
			/**
			 * Retrieves <code>Validator</code> list.
			 * @return the <code>Validator</code> list
			 */
			public function get validators() :Array {
				var _validators :Array = [];
				for each(var item:FormItem in items){
					for (var i:int=0; i < item.validators.length;i++)	{
						_validators.push(item.validators[i]);
					}
				}
				return _validators;
			}
			
			public function get vizualizer():IValidationVisualizer
			{
				return _visualizer;
			}
			
			public function set vizualizer(value:IValidationVisualizer):void
			{
				_visualizer = value;
			}
			
			/**
			 * Overrides parent function to allow gathering <code>FormItem</code> validators and etc.
			 */
			override protected function createChildren():void
			{
				super.createChildren();
				enumerateChildren(this);
				_initialized = true;
				commitReadOnly();
			}
			
			private function commitReadOnly():void
			{
				if (!_readOnlySet) return;
				if (!_initialized) return;
				_readOnlySet = false;
				for each(var item:FormItem in items){
					item.readOnly = _readOnly;
				}
			}
			
			/**
			 * Performs form initialization after creation.<br/>
			 * @param event the event to trigger invocation
			 */
			private function creationCompleteHandler(event:Event):void
			{
				commitReadOnly();
			}
			
			/**
			 * Walk through nodes to gather <code>FormItem</code> nodes. 
			 * @param parent parent <code>Container</code>
			 */
			private function enumerateChildren(parent:Object):void
			{
				if (parent is FormItem){
					items.push(parent);
				}
				if (parent is Container){
					var children:Array = parent.getChildren();
					for(var i:int = 0; i < children.length; i++) {
						enumerateChildren(children[i]);
					}
				}
			}
			
			
			/**
			 * Gathers form item's values to populate form bean with it.
			 */
			private function gatherFormObject():void
			{
				if(null != _formObjectClass){
					var type:Type = Type.forClass(_formObjectClass);
					_formObject = ClassUtils.newInstance(_formObjectClass);
					var accessors:Array = type.accessors;
					//walk though form items to populate form bean with values
					var formItemsLength:int = items.length;
					for(var i:int=0; i<formItemsLength; i++){
						var propertyName:String = FormItem(items[i]).name;
						var methodName:String = "set"+propertyName.charAt(0).toUpperCase()+propertyName.substr(1, propertyName.length);
						if(null != propertyName && null != _formObject && null != type.getMethod(methodName)){
							var methodInvoker:MethodInvoker = new MethodInvoker();
							methodInvoker.arguments = [FormItem(items[i]).data];
							methodInvoker.method = methodName;
							methodInvoker.target = _formObject;
							methodInvoker.invoke();
						}
					}
				}
			}
			
			/**
			 * Visualize validation process.
			 * @param formItem the <code>FormItem</code> to validate data of.
			 * @param validationResult the <code>ValidationResultEvent</code>.
			 */
			private function visualizeValidation(formItem:FormItem, validationResult:ValidationResultEvent):void
			{
				if (null != _visualizer){
					_visualizer.visualize(formItem, validationResult);
				}
			}
			
		}
}